Skip to content

perf: closest() and near() use a matrix of coordinates, not a loop - #81

Merged
petercorke merged 2 commits into
mainfrom
perf/closest-vectorized
Oct 31, 2026
Merged

petercorke merged 2 commits into
mainfrom
perf/closest-vectorized

Conversation

@petercorke

Copy link
Copy Markdown
Owner

Refs #74.

What

closest() and near() measured the metric of every vertex in a Python loop unless build_kdtree() had been called, and any add_vertex discards a tree, so a planner that interleaves add_vertex with closest (RTB's LatticePlanner, RRTPlanner) always paid for the loop.

The graph now keeps the coordinates of its embedded vertices as a matrix (_PointCache): built on first use, appended to by add_vertex, and the built-in metrics (L1, L2, SE2) are applied to all rows at once via the existing _metric_columns. A metric of your own is still applied to each vertex in turn. It is not pickled or copied and has its own staleness flag (not _spatialchange).

Measured

main this branch
closest, N=1,000 (examples/benchmark.py) 726 us 11.9 us
add_vertex 0.85 us 0.79 us (noise)
LatticePlanner.plan, 1,292 vertices 1.07 s 0.06 s
RRTPlanner.plan, 300 nodes 5.1 s 4.3 s

Vertices, edges and paths of the RTB RRT, PRM and Lattice planners are identical to main (digest of every coordinate and edge). RTB tests/test_mobile.py and MVTB tests/test_bundleadjust.py pass against this branch; make -C docs html builds.

Behaviour to know about

  • A coordinate changed in place (v.coord[0] = x) is not seen by closest/near, as with build_kdtree(). Assigning v.coord = x (or +=) is seen. Documented in the closest docstring; there is a test pinning it.
  • Removing a vertex or assigning v.coord on a vertex already in the graph drops the matrix, and the next query rebuilds it in O(n). That is still about 6x faster than the old loop (0.21 ms against 1.34 ms at n=2,000, measured), but the constructor form add_vertex(coord=x) keeps the matrix and is ~15x faster again. Not changed here: nothing in RTB or MVTB assigns coord after adding.
  • Coordinates that are not finite are left out of the matrix (the loop never picked them either); coordinates that are not all vectors of one length fall back to the loop, so the original errors still surface.

Tests

tests/test_point_cache.py (17 tests, expectations from a hand-written oracle): interleaved add/query across a buffer growth, L1/L2/SE2, ties, vertices without coordinates, removal, coord assignment, the in-place limit, non-finite and odd-shaped coordinates, user metric, near, pickle/copy/subgraph. 788 tests pass, mypy src is clean, and the changed lines are covered.

🤖 Generated with Claude Code

Without a k-d tree both measured the metric of every vertex in a Python
loop, and any add_vertex discarded the tree, so a planner that interleaves
add_vertex with closest (RTB's LatticePlanner, RRTPlanner) always paid for
the loop.

The graph now keeps the coordinates of its embedded vertices as a matrix,
built on first use and appended to by add_vertex, and applies the built-in
metrics to all rows at once.  Removing a vertex or assigning a coord drops
it.  A metric of your own is still applied to each vertex in turn.  It is
not pickled or copied, and has its own staleness flag.

closest at N=1,000: 726 us -> 12 us.  Lattice plan of 1,292 vertices: 1.07 s
-> 0.06 s.  Vertices, edges and paths of the RTB planners are unchanged.

A coordinate changed in place (v.coord[0] = x) is not seen, as with
build_kdtree(); documented.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 42 complexity · 0 duplication

Metric Results
Complexity 42
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@codecov

codecov Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.12%. Comparing base (ffb259d) to head (5c9cbbb).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #81      +/-   ##
==========================================
+ Coverage   98.05%   98.12%   +0.06%     
==========================================
  Files           4        4              
  Lines        1590     1649      +59     
==========================================
+ Hits         1559     1618      +59     
  Misses         31       31              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@petercorke
petercorke merged commit 8d5ba07 into main Oct 31, 2026
14 checks passed
@petercorke
petercorke deleted the perf/closest-vectorized branch October 31, 2026 03:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant